#include <algorithm>
#include <bitset>
#include <cassert>
#include <cctype>
#include <chrono>
#include <cmath>
#include <complex>
#include <cstring>
#include <deque>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

using namespace std;

using int64 = long long;

#define all(_) begin(_), end(_)
#define rall(_) rbegin(_), rend(_)

template <class T>
class graph {
 public:
  graph(int n) : n_(n) { data_.resize(n_); }
  struct edge {
    int from, to;
    T cost;

    edge(int from_, int to_, T cost_) : from(from_), to(to_), cost(cost_) {}
  };
  int n_;
  std::vector<std::vector<int>> data_;
  std::vector<edge> edges_;

  virtual void add(int from, int to, T cost) = 0;
};

template <class T>
class forest : public graph<T> {
 public:
  using graph<T>::n_;
  using graph<T>::data_;
  using graph<T>::edges_;

  forest(int n) : graph<T>(n) {}

  void add(int from, int to, T cost = 1) {
    assert(0 <= from && from < n_ && 0 <= to && to < n_);
    int id = edges_.size();
    assert(id < n_ - 1);
    data_[from].emplace_back(id);
    data_[to].emplace_back(id);
    edges_.emplace_back(from, to, cost);
  }
};

template <class T>
class digraph : public graph<T> {
 public:
  using graph<T>::n_;
  using graph<T>::data_;
  using graph<T>::edges_;

  digraph(int n) : graph<T>(n) {}

  void add(int from, int to, T cost = 1) {
    assert(0 <= from && from < n_ && 0 <= to && to < n_);
    int id = edges_.size();
    data_[from].emplace_back(id);
    edges_.emplace_back(from, to, cost);
  }

  digraph<T> reverse() const {
    digraph<T> rev(n_);
    for (const auto& [from, to, cost] : edges_) {
      rev.add(to, from, cost);
    }
    return rev;
  }
};

template <class T>
class undigraph : public graph<T> {
 public:
  using graph<T>::n_;
  using graph<T>::data_;
  using graph<T>::edges_;

  undigraph(int n) : graph<T>(n) {}

  void add(int from, int to, T cost = 1) {
    assert(0 <= from && from < n_ && 0 <= to && to < n_);
    int id = edges_.size();
    data_[from].emplace_back(id);
    data_[to].emplace_back(id);
    edges_.emplace_back(from, to, cost);
  }
};

digraph<int> grid_to_digraph(const std::vector<std::string>& sv) {
  int h = sv.size(), w = sv[0].size();
  digraph<int> g(h * w);
  constexpr int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
  auto Out = [&](int x, int y) { return (x < 0 || x >= h || y < 0 || y >= w); };
  auto Compress = [&](int x, int y) { return x * w + y; };
  for (int i = 0; i < h; ++i) {
    for (int j = 0; j < w; ++j) {
      for (int k = 0; k < 4; ++k) {
        int nx = i + dx[k], ny = j + dy[k];
        if (Out(nx, ny)) continue;
        if (sv[i][j] == '.' && sv[nx][ny] == '.') {
          g.add(Compress(i, j), Compress(nx, ny));
        }
      }
    }
  }
  return g;
}

template <class T>
std::vector<T> bfs(const graph<T>& g, int s) {
  std::vector<T> dist(g.n_, -1);
  dist[s] = 0;
  std::queue<int> que;
  que.emplace(s);
  while (!que.empty()) {
    int cur = que.front();
    que.pop();
    for (int id : g.data_[cur]) {
      const auto& [from, to, cost] = g.edges_[id];
      int nxt = from ^ to ^ cur;
      if (dist[nxt] != -1) continue;
      dist[nxt] = dist[cur] + cost;
      que.emplace(nxt);
    }
  }
  return dist;
}

template <class T>
inline void eraque(std::vector<T>& vec) {
  vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
  return;
}

template <class T>
inline int lower_position(const std::vector<T>& vec, T val) {
  return static_cast<int>(std::distance(
      vec.begin(), std::lower_bound(vec.begin(), vec.end(), val)));
}

template <class T>
inline int upper_position(const std::vector<T>& vec, T val) {
  return static_cast<int>(std::distance(
      vec.begin(), std::upper_bound(vec.begin(), vec.end(), val)));
}

template <class T>
inline std::string to_binary(T n) {
  assert(n > 0);
  std::string ret = "";
  while (n) ret += (n & 1) ? '1' : '0', n >>= 1;
  std::reverse(ret.begin(), ret.end());
  return ret;
}

template <class T>
inline void println(T val) {
  std::cout << val << '\n';
}

template <class T>
inline void println(const std::vector<T>& vec) {
  int sz = vec.size();
  for (int i = 0; i < sz; ++i) std::cout << vec[i] << " \n"[i == sz - 1];
}

template <class T>
using binary_heap = std::priority_queue<T, std::vector<T>, std::greater<T>>;

template <class T>
std::istream& operator>>(std::istream& is, std::vector<T>& vec) {
  for (T& e : vec) is >> e;
  return is;
}

template <class T, class U>
std::istream& operator>>(std::istream& is, std::pair<T, U>& p) {
  return is >> p.first >> p.second;
}

template <class Tuple, std::size_t... Is>
void tuple_in(std::istream& is, Tuple& tp, std::index_sequence<Is...>) {
  ((is >> std::get<Is>(tp)), ...);
}

template <class... Args>
std::istream& operator>>(std::istream& is, std::tuple<Args...>& tp) {
  tuple_in(is, tp, std::index_sequence_for<Args...>{});
  return is;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);

  int h, w; cin >> h >> w;
  vector<string> s(h); cin >> s;
  digraph<int> g = grid_to_digraph(s);
  int ans = 0;
  for (int i = 0; i < h; ++i) {
    for (int j = 0; j < w; ++j) {
      if (s[i][j] == '.') {
        vector dist = bfs(g, i * w + j);
        ans = max(ans, *max_element(all(dist)));
      }
    }
  }

  println(ans);

  return 0;
}


Measured memory_bytes: 3640